package Model

import (
	"encoding/csv"
	"fmt"
	"github.com/xuri/excelize/v2"
	"log"
	"os"
	"strconv"
	"sync"
	"time"
	"wskyygyso/casually/AppInit"
)

/**
Book
*/
type Book struct {
	BookId   int    `gorm:"column:book_id" json:"book_id"`
	BookName string `gorm:"column:book_name" json:"book_name"`
}

/**
BookList
*/
type BookList struct {
	Data []*Book `json:"data"`
	Page int     `json:"page"`
}

/**
sqlDataChan 从数据库获取到的数据 channel
*/
type sqlDataChan chan *BookList

/**
resultChan 结果channel
*/
type resultChan chan *Result

/**
ReadSqlDataFunc 读取数据方法
*/
type ReadSqlDataFunc func() sqlDataChan

/**
ResultFunc 处理结果方法
*/
type ResultFunc func(input sqlDataChan) resultChan

/**
Result
*/
type Result struct {
	Err  error
	Page int
}

const sql = "select book_id,book_name from books order by book_id limit ? offset ? "

/**
ReadData 同步模式
*/
func ReadData() {
	page := 1
	pageSize := 10000
	for {
		bookList := &BookList{make([]*Book, 0), page}
		db := AppInit.GetDb().Raw(sql, pageSize, (page-1)*pageSize).Find(&bookList.Data)
		if db.Error != nil {
			log.Fatal(db.Error)
		}
		if db.RowsAffected == 0 {
			log.Println("无数据")
			break
		}
		err := SaveExcel(bookList)
		if err != nil {
			log.Fatal(err)
		}
		page++
	}
}

/**
SaveData 导出csv
*/
func SaveData(list *BookList) error {
	//. 对应的是执行文件的目录
	file := fmt.Sprintf("./csv/%d.csv", list.Page)
	csvFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		return err
	}
	defer csvFile.Close()
	w := csv.NewWriter(csvFile)
	header := []string{"book_id", "book_name"}
	export := [][]string{
		header,
	}
	for _, d := range list.Data {
		cnt := []string{
			strconv.Itoa(d.BookId),
			d.BookName,
		}
		export = append(export, cnt)
	}
	err = w.WriteAll(export)
	if err != nil {
		return err
	}
	w.Flush()
	time.Sleep(500 * time.Millisecond)
	return nil
}

/**
SaveExcel  导出excel
*/
func SaveExcel(list *BookList) error {
	file := excelize.NewFile()
	fileName := fmt.Sprintf("./csv/%d.xlsx", list.Page)
	index := file.NewSheet("书籍")
	file.SetCellValue("书籍", "A1", "book_id")
	file.SetCellValue("书籍", "B1", "book_name")
	for index, book := range list.Data {
		file.SetCellValue("书籍", "A"+strconv.Itoa(index+2), book.BookId)
		file.SetCellValue("书籍", "B"+strconv.Itoa(index+2), book.BookName)
	}
	file.SetActiveSheet(index)
	if err := file.SaveAs(fileName); err != nil {
		fmt.Println(err)
		return err
	}
	return nil
}

/**
DataChan 写入数据到channel
*/
func DataChan() sqlDataChan {
	out := make(sqlDataChan)
	go func() {
		defer func() {
			close(out)
		}()
		page := 1
		pageSize := 100
		for {
			bookList := &BookList{make([]*Book, 0), page}
			db := AppInit.GetDb().Raw(sql, pageSize, (page-1)*pageSize).Find(&bookList.Data)
			if db.Error != nil {
				log.Fatal(db.Error)
			}
			if db.RowsAffected == 0 {
				log.Println("无数据")
				break
			}
			out <- bookList
			page++
		}
	}()
	return out
}

/**
ReadChan 读取channel的数据 并开启协程处理数据并将结果写入channel
*/
func ReadChan(input sqlDataChan) resultChan {
	out := make(resultChan)
	go func() {
		defer func() {
			close(out)
		}()
		result := &Result{}
		for bookList := range input {
			err := SaveExcel(bookList)
			result.Page = bookList.Page
			if err != nil {
				result.Err = err
			}
			out <- result
		}
	}()
	return out
}

/**
ReadChanData chan模式
*/
func ReadChanData() chan *Result {
	dataChan := DataChan()
	out := ReadChan(dataChan)
	return out
}

/**
Pipe 管道模式
*/
func Pipe(input ReadSqlDataFunc, out ...ResultFunc) resultChan {
	inChan := input()
	res := make(resultChan)
	wg := sync.WaitGroup{}
	for _, fn := range out {
		getChan := fn(inChan)
		wg.Add(1)
		go func(out resultChan) {
			defer func() {
				wg.Done()
			}()
			for o := range out {
				res <- o
			}
		}(getChan)

	}
	go func() {
		defer close(res)
		wg.Wait()
	}()
	return res
}

/**
Pipe2 管道模式2
*/
func Pipe2(input ReadSqlDataFunc, outFunc ResultFunc, number int) resultChan {
	inChan := input()
	res := make(resultChan)
	wg := sync.WaitGroup{}
	for i := 1; i <= number; i++ {
		wg.Add(1)
		go func() {
			defer func() {
				wg.Done()
			}()
			out := outFunc(inChan)
			for o := range out {
				res <- o
			}
		}()
	}
	go func() {
		defer close(res)
		wg.Wait()
	}()
	return res
}
